using System;
using NUnit.Framework;
using Tycho.Lexer;
using Tycho.Parser.Peg;
using Tycho.Parser.Tokens;

namespace Tycho.Parser.Tests.Peg {
    [TestFixture]
    public class ProductionSpecification {
        [Test]
        public void ParseShouldCacheResultAndNeverCallReallyParseAgainForSameIndex () {
            IdentifierTerm expectedTerm1 = new IdentifierTerm ("a");
            IdentifierTerm expectedTerm2 = new IdentifierTerm ("b");
            FakeProduction prod = new FakeProduction ();

            Token[] tokens = NativeLexer.Lex ("a b c").ToArray ();

            prod.BuildTerm = () => expectedTerm1;
            var result = prod.Parse (tokens, 0, null);
            Assert.AreSame (expectedTerm1, result.Term);
            Assert.AreEqual (1, prod.TimesReallyParseCalled);
            Assert.AreEqual (1, result.Index);

            prod.BuildTerm = () => null;
            result = prod.Parse (tokens, 0, null);
            Assert.AreSame (expectedTerm1, result.Term);
            Assert.AreEqual (1, prod.TimesReallyParseCalled);
            Assert.AreEqual (1, result.Index);

            prod.BuildTerm = () => expectedTerm2;
            result = prod.Parse (tokens, 1, null);
            Assert.AreSame (expectedTerm2, result.Term);
            Assert.AreEqual (2, prod.TimesReallyParseCalled);
            Assert.AreEqual (2, result.Index);

            prod.BuildTerm = () => null;
            result = prod.Parse (tokens, 1, null);
            Assert.AreSame (expectedTerm2, result.Term);
            Assert.AreEqual (2, prod.TimesReallyParseCalled);
            Assert.AreEqual (2, result.Index);

            prod.BuildTerm = () => null;
            result = prod.Parse (tokens, 2, null);
            Assert.IsNull (result);
            Assert.AreEqual (3, prod.TimesReallyParseCalled);

            prod.BuildTerm = () => null;
            result = prod.Parse (tokens, 2, null);
            Assert.IsNull (result);
            Assert.AreEqual (3, prod.TimesReallyParseCalled);
        }
    }

    public class FakeProduction : Production<object> {
        public int TimesReallyParseCalled = 0;
        public Func<Term> BuildTerm;

        protected override ParseResult<object> ReallyParse (Token[] tokens, int index, object context) {
            TimesReallyParseCalled++;
            Term term = BuildTerm ();

            if (term != null) {
                return new ParseResult<object> (index + 1, term, context);
            } else {
                return null;
            }
        }
    }
}